﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Reflection;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;

//CV
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;

namespace CropPic
{
    public partial class CutImage : Form
    {
        public CutImage()
        {
            InitializeComponent();
        }

        //定义两个管理类，分别管理图片和视频
        private PicManager picMan;
        private VideoManager vidMan;

        
        double dWRate;
        double dHRate;
        //缩放率
        double scale;

        bool isVideo = false;          

        //鼠标状态，false为松开
        private bool mouseStatus = false;
        
        //鼠标按下的点
        private Point startPoint;
        //鼠标松开的点
        private Point endPoint;
        
        
        //最大重绘矩形的上下左右的坐标，这样重绘的效率更高。
        private int minStartX, minStartY, maxEndX, maxEndY;

        private FileDir fileDir;
        //是否显示矩形
        private bool showRect = false;
        //当前正在绘制的矩形
        private Rectangle currRect;

        //存储所有画过的矩形
        private List<Rectangle> rectList = new List<Rectangle>();
        private List<Point> pointList = new List<Point>();
        List<Image<Bgr, Byte>> frameList = new List<Image<Bgr, Byte>>();
        
        //截取的图像保持一定的长宽比
        //const double whScale = 0.8;
        


        /// <summary>
        /// 剪切图片
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnOpen_Click(object sender, EventArgs e)
        {
            //初始化
            isVideo = false;
            rectList.Clear();
            pointList.Clear();

            //设置视频帧数按钮失效
            txtSetPos.Visible = false;
            btnSetPos.Visible = false;

            string strFilename = "";
            FolderBrowserDialog fldOpen = null;
            try
            {
                fldOpen = new FolderBrowserDialog();
                fldOpen.Description = "选择要打开文件的目录";
  
                //folderOpen.Filter = "All Image Files(*.*)|*.*";
                if (lblOpen.Text != "")
                {
                    fldOpen.SelectedPath = lblOpen.Text;
                }
                else
                {
                    //指定对话框默认显示的根目录地址 注意RootFolder的接收数据类型   
                    fldOpen.RootFolder = Environment.SpecialFolder.MyComputer;
                }

                if (fldOpen.ShowDialog() == DialogResult.OK)
                {
                    string folderPath = fldOpen.SelectedPath;

                    if (folderPath != "")
                    {
                        lblOpen.Text = folderPath;
                        //用目录初始化PicManager类
                        picMan = new PicManager(folderPath);
                        showPic();
                    }
                    else
                    {
                        MessageBox.Show("没有目录文件");
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("没有目录文件(" + strFilename + ")\r\n" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                if (fldOpen != null)
                    fldOpen.Dispose();
                fldOpen = null;
            }
        }


        /**
         * 显示当前filePoint指向的文件
         * */
        private void showPic()
        {
            if (picMan.isFinished())
            {
                MessageBox.Show("图片已经处理完了");
                return;
            }

            pbxOriImage.Image = picMan.getPic();
            
            lblPos.Text = "第 " + (picMan.FilePoint+1) + "/" + (picMan.FileLen) + " 幅";
            this.SetImageSizeMode(pbxOriImage);
            
            pbxOriImage.Invalidate();

        }


        /// <summary>
        /// 设置图片显示模式
        /// </summary>
        /// <param name="pImage"></param>
        private void SetImageSizeMode(PictureBox pImage)
        {
            if (pImage == null) return;
            Image imgResult = pImage.Image;
            if (imgResult != null)
            {
                //控制图片显示方式
                if (imgResult.Width > pImage.Width || imgResult.Height > pImage.Height)
                {
                    pImage.SizeMode = PictureBoxSizeMode.Zoom;
                }
                else
                {
                    pImage.SizeMode = PictureBoxSizeMode.CenterImage;
                }
            }
        }


        /// <summary>
        /// 获取图片实际显示的大小
        /// </summary>
        /// <param name="pbxImage"></param>
        /// <returns></returns>
        public Rectangle GetPictureDisplaySize(PictureBox pbxImage)
        {
            if (pbxImage != null)
            {
                PropertyInfo ppiImageRect = pbxImage.GetType().GetProperty("ImageRectangle", BindingFlags.Instance | BindingFlags.NonPublic);
                return (Rectangle)ppiImageRect.GetValue(pbxImage, null);
            }
            return new Rectangle(0, 0, 1, 1);
        }

        /// <summary>
        /// 获取当前文件的根目录
        /// </summary>
        /// <returns>string: 返回文件的根目录</returns>
        public string getRootFold(){
            string rootFold = null;
            if (lblSave.Text != "")
            {
                rootFold = lblSave.Text;
            }
            else
            {
                if (isVideo)
                {
                    rootFold = vidMan.VideoName.Substring(0, vidMan.VideoName.LastIndexOf('\\'));
                }
                else
                {
                    rootFold = lblOpen.Text;
                }
            }
            return rootFold;
        }


        /**
         * 保存图片上的裁剪信息
         **/
        private void btnSure_Click(object sender, EventArgs e)
        {
            if (pbxOriImage.Image == null)
            {
                return;
            }

            //获取保存文件的根目录，并创建保存目录
            string rootFold = getRootFold();
            fileDir = new FileDir(rootFold);
            
            if (isVideo)
            {
                //保存视频帧
                if (vidMan.FramePoint < vidMan.TotalFrames)
                {
                    string dir = vidMan.VideoName.Substring(0, vidMan.VideoName.LastIndexOf('\\'));
                    fileDir.VideoDir = dir + "\\image\\";
                    fileDir.mkVidDir();

                    fileDir.ImageOutputName = fileDir.VideoDir + String.Format("I{0:00000}", vidMan.FramePoint / vidMan.FrameStep+1);
                    fileDir.FileOutputName = fileDir.CropPicFolder + String.Format("I{0:00000}", vidMan.FramePoint / vidMan.FrameStep+1);
                    fileDir.FileAnnoName = fileDir.AnnotationFolder + String.Format("I{0:00000}", vidMan.FramePoint / vidMan.FrameStep+1) + ".txt";
                    
                    //保存这张图片上的信息
                    saveSimplePic();

                    //显示下一帧视频
                    if (!vidMan.toNext())
                    {
                        MessageBox.Show("视频已经播放完了");
                    }

                    showRect = false;
                    rectList.Clear();
                    pointList.Clear();
                    showVideo();
                }
            }
            else
            {
                fileDir.mkFold();
                //保存下剪裁的图片,显示下一张图片
                if (!picMan.isFinished())
                {
                    string strFilename = picMan.getName();
                    strFilename = strFilename.Substring(0, strFilename.IndexOf('.'));

                    fileDir.FileOutputName = fileDir.CropPicFolder + strFilename;
                    fileDir.FileAnnoName = fileDir.AnnotationFolder + strFilename + ".txt";

                    //保存这张图片上的信息
                    //savePic();
                    saveSimplePic();

                    //清除矩形和点信息
                    showRect = false;
                    rectList.Clear();
                    pointList.Clear();

                    //显示下一张图片
                    if (!picMan.toNext())
                    {
                        MessageBox.Show("图片已经处理完了。");
                    }
                    showPic();
                }
            }
        }


 
        ///<summary>
        ///<remarks>保存简单annotation形式的图片</remarks>
        ///</summary>

        private void saveSimplePic()
        {
            pbxOriImage.Image.Save(fileDir.ImageOutputName + ".jpg");
            int i = 1;
            //下面求这幅图片的缩放率
            Rectangle rectDisplay = this.GetPictureDisplaySize(pbxOriImage);        //图片实际显示的大小
            dWRate = (double)pbxOriImage.Image.Width / pbxOriImage.Size.Width;
            dHRate = (double)pbxOriImage.Image.Height / pbxOriImage.Size.Height;  //缩放比例：高
            scale = dWRate > dHRate ? dWRate : dHRate;
            double offsetX = ((double)pbxOriImage.Size.Width - rectDisplay.Width) / 2;
            double offsetY = ((double)pbxOriImage.Size.Height - rectDisplay.Height) / 2;

            //StreamWriter sw = File.AppendText(fileAnnoName);
            StreamWriter sw = new StreamWriter(fileDir.FileAnnoName);
            try
            {
                String w = "%bbGt version=3\r\n";

                foreach (Rectangle rect in rectList)
                {
                    if (rect.Width == 0 || rect.Height == 0)
                        continue;

                    int realWidth, realHeight, realX, realY;
                    if (scale <= 1)
                    {
                        //如果没有缩放，则只计算偏移即可
                        realWidth = rect.Width;
                        realHeight = rect.Height;
                        //计算在图片上的真实起点
                        realX = (int)((rect.X - offsetX));
                        realY = (int)((rect.Y - offsetY));

                    }
                    else
                    {
                        realWidth = (int)(rect.Width * scale);
                        realHeight = (int)(rect.Height * scale);
                        //计算在图片上的真实起点
                        realX = (int)((rect.X - offsetX) * scale);
                        realY = (int)((rect.Y - offsetY) * scale);
                    }
                    realX = realX >= 0 ? realX : 0;
                    realY = realY >= 0 ? realY : 0;
                    foreach (Point p in pointList)
                    {

                        if (p.X > rect.X && p.X < rect.X + rect.Width
                            && p.Y > rect.Y && p.Y < rect.Y + rect.Height)
                        {
                            //只有被在该矩形框中的点才被算作Rectanle的Center
                            int realpX = (int)((p.X - offsetX) * scale);
                            int realpY = (int)((p.Y - offsetY) * scale);
                            //w2 += "Center point on object " + i + " \"PASperson\" (X, Y) : (" + realpX + ", " + realpY + ")\r\n";
                        }

                    }


                    w += String.Format("person {0} {1} {2} {3} 0 0 0 0 0 0 0\r\n", realX, realY, realWidth, realHeight);


                    Bitmap bmpDest = new Bitmap(realWidth, realHeight, PixelFormat.Format32bppRgb);       //目标图片大小
                    Graphics g = Graphics.FromImage(bmpDest);               //创建GDI

                    Rectangle rectDest = new Rectangle(0, 0, realWidth, realHeight);
                    Rectangle rectSource = new Rectangle(realX, realY, realWidth, realHeight);

                    g.DrawImage(pbxOriImage.Image, rectDest, rectSource, GraphicsUnit.Pixel);               //绘图

                    bmpDest.Save(fileDir.FileOutputName + "_" + i.ToString() + ".jpg");
                    i++;
                    g.Dispose();
                }

                sw.Write(w);
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
            finally
            {
                sw.Close();
            }

        }

        /**
        * 保存图片
        * */
        private void savePic()
        {
            int i = 1;
            //下面求这幅图片的缩放率
            Rectangle rectDisplay = this.GetPictureDisplaySize(pbxOriImage);        //图片实际显示的大小
            dWRate = (double)pbxOriImage.Image.Width / pbxOriImage.Size.Width;
            dHRate = (double)pbxOriImage.Image.Height / pbxOriImage.Size.Height;  //缩放比例：高
            scale = dWRate > dHRate ? dWRate : dHRate;
            double offsetX = ((double)pbxOriImage.Size.Width - rectDisplay.Width) / 2;
            double offsetY = ((double)pbxOriImage.Size.Height - rectDisplay.Height) / 2;

            if (rectList.Count > 0)
            {
                String fileName = "";
                if (isVideo)
                {
                    fileName = vidMan.VideoName;
                }
                else
                {
                    fileName = picMan.getFullPath();
                }

                //StreamWriter sw = File.AppendText(fileAnnoName);
                StreamWriter sw = new StreamWriter(fileDir.FileAnnoName);
                try
                {
                    String w = "# PASCAL Annotation Version 1.00\r\n\r\n" + "Image filename : \"" + fileName + "\"\r\n";
                    String w2 = "";
                    w += "Image size (X x Y x C) : " + pbxOriImage.Image.Width + " x "
                        + pbxOriImage.Image.Height + " x 3\r\n" + "Database: \"The soochow Person Database\"\r\n";


                    foreach (Rectangle rect in rectList)
                    {
                        if (rect.Width == 0 || rect.Height == 0)
                            continue;
                        
                        int realWidth, realHeight, realX, realY;
                        if (scale <= 1)
                        {
                            //如果没有缩放，则只计算偏移即可
                            realWidth = rect.Width;
                            realHeight = rect.Height;
                            //计算在图片上的真实起点
                            realX = (int)((rect.X - offsetX));
                            realY = (int)((rect.Y - offsetY));
                        }
                        else
                        {
                            realWidth = (int)(rect.Width * scale);
                            realHeight = (int)(rect.Height * scale);
                            //计算在图片上的真实起点
                            realX = (int)((rect.X - offsetX) * scale);
                            realY = (int)((rect.Y - offsetY) * scale);
                        }

                        w2 += "\r\n\r\n# Details for object " + i + " (\"PASperson\")\r\n";
                        w2 += "# Center point -- not available in other PASCAL databases -- refers\r\n" + "# to person head center\r\n";
                        w2 += "Original label for object " + i + " \"PASperson\" : \"UprightPerson\"\r\n";
                        foreach (Point p in pointList)
                        {

                            if (p.X > rect.X && p.X < rect.X + rect.Width
                                && p.Y > rect.Y && p.Y < rect.Y + rect.Height)
                            {
                                //只有被在该矩形框中的点才被算作Rectanle的Center
                                int realpX = (int)((p.X - offsetX) * scale);
                                int realpY = (int)((p.Y - offsetY) * scale);
                                w2 += "Center point on object " + i + " \"PASperson\" (X, Y) : (" + realpX + ", " + realpY + ")\r\n";
                            }

                        }

                        w2 += "Bounding box for object " + i + " \"PASperson\" (Xmin, Ymin) - (Xmax, Ymax) : (" + realX + ", " + realY + ") - (" + (realX + realWidth) + ", " + (realY + realHeight) + ")";


                        Bitmap bmpDest = new Bitmap(realWidth, realHeight, PixelFormat.Format32bppRgb);       //目标图片大小
                        Graphics g = Graphics.FromImage(bmpDest);               //创建GDI

                        Rectangle rectDest = new Rectangle(0, 0, realWidth, realHeight);
                        Rectangle rectSource = new Rectangle(realX, realY, realWidth, realHeight);

                        g.DrawImage(pbxOriImage.Image, rectDest, rectSource, GraphicsUnit.Pixel);               //绘图

                        bmpDest.Save(fileDir.FileOutputName + "_" + i.ToString() + ".jpg");
                        i++;
                        g.Dispose();
                    }
                    w += "Objects with groud truth : " + (i - 1) + "\r\n\r\n# Note that there might be other objects in the image\r\n"
                        + "# for which ground truth data has not been provided.\r\n\r\n" + "# Top left pixel co-ordinates : (0, 0)";
                    w += w2;
                    sw.Write(w);
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message);
                }
                finally
                {
                    sw.Close();
                }
            }

        }


        /// <summary>
        /// 记录鼠标按下去的位置
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pbxOriImage_MouseDown(object sender, MouseEventArgs e)
        {
            currRect = new Rectangle();
            if (pbxOriImage.Image == null) return;
            mouseStatus = true;
            showRect = true;
            startPoint.X = e.X;
            startPoint.Y = e.Y;
            //重新一个矩形，重置最大重绘矩形的上下左右的坐标
            minStartX = e.X;
            minStartY = e.Y;
            maxEndX = e.X;
            maxEndY = e.Y;
        }


        /**
         * 鼠标起来，记录矩形
         * */
        private void pbxOriImage_MouseUp(object sender, MouseEventArgs e)
        {
            if (mouseStatus)
            {
                mouseStatus = false;
                endPoint.X = e.X; endPoint.Y = e.Y;
                int realStartX = Math.Min(startPoint.X, endPoint.X);
                int realStartY = Math.Min(startPoint.Y, endPoint.Y);
                int realEndX = Math.Max(startPoint.X, endPoint.X);
                int realEndY = Math.Max(startPoint.Y, endPoint.Y);
                int width = realEndX - realStartX;
                int height = realEndY - realStartY;
                if (width == 0 && height == 0)
                {
                    //是一个点
                    pointList.Add(new Point(realStartX, realStartY));
                }
                else if (width != 0 && height != 0)
                {
                    currRect = new Rectangle(realStartX, realStartY, width, height);
                    //当前矩形算是被认可了，所以存起来
                    rectList.Add(currRect);
                }
                pbxOriImage.Invalidate();
            }
        }



        private void pbxOriImage_MouseMove(object sender, MouseEventArgs e)
        {
            
            if (pbxOriImage.Image == null) return;
            if (mouseStatus)
            {
                endPoint.X = e.X; endPoint.Y = e.Y;
                //这一段是获取要绘制矩形的上下左右的坐标，如果不这样处理的话，只有从左上开始往右下角才能画出矩形。
                //这样处理的话，可以任意方向，当然中途可以更换方向。
                int realStartX = Math.Min(startPoint.X, endPoint.X);
                int realStartY = Math.Min(startPoint.Y, endPoint.Y);
                int realEndX = Math.Max(startPoint.X, endPoint.X);
                int realEndY = Math.Max(startPoint.Y, endPoint.Y);

                minStartX = Math.Min(minStartX, realStartX);
                minStartY = Math.Min(minStartY, realStartY);
                maxEndX = Math.Max(maxEndX, realEndX);
                maxEndY = Math.Max(maxEndY, realEndY);

                currRect = new Rectangle(realStartX, realStartY, realEndX - realStartX, realEndY - realStartY);
                //一下是为了获取最大重绘矩形。
                Rectangle refeshRect = new Rectangle(minStartX, minStartY, maxEndX - minStartX, maxEndY - minStartY);
                refeshRect.Inflate(1, 1);//重绘矩形的大小扩展1个单位
            }

            pbxOriImage.Invalidate();//重绘


        }

        /// <summary>
        /// 绘制矩形选择框
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pbxOriImage_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            if (pbxOriImage.Image != null)
            {
                if (showRect)
                {
                    foreach (Point circle in pointList)
                    {
                        g.DrawEllipse(new Pen(Color.Red, 4), circle.X, circle.Y, 1, 1);
                    }
                    if (mouseStatus == true) 
                    { 
                        g.DrawRectangle(new Pen(Color.Green, 2), currRect);
                    }
                    foreach (Rectangle rect in rectList)
                    {
                        g.DrawRectangle(new Pen(Color.Green, 2), rect);
                    }
                }

            }

        }


        /// <summary>
        /// 保存剪切后的图片
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSave_Click(object sender, EventArgs e)
        {
            string strFilename = "";
            FolderBrowserDialog fldOpen = null;
            try
            {
                fldOpen = new FolderBrowserDialog();
                fldOpen.Description = "选择要保存图片的目录";

                if (lblSave.Text != "")
                {
                    fldOpen.SelectedPath = lblSave.Text;
                }
                else
                {
                    //指定对话框默认显示的根目录地址 注意RootFolder的接收数据类型   
                    fldOpen.RootFolder = Environment.SpecialFolder.MyComputer;
                }

                if (fldOpen.ShowDialog() == DialogResult.OK)
                {
                    string folderPath = fldOpen.SelectedPath;
                    if (folderPath != "")
                    {
                        lblSave.Text = folderPath;
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("无法选择目录(" + strFilename + ")\r\n" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                if (fldOpen != null)
                    fldOpen.Dispose();
                fldOpen = null;
            }
        }

        private void CutImage_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode != Keys.PageUp && e.KeyCode != Keys.PageDown)
                return;
            if (!isVideo)
            {
                //图片处理
                if (e.KeyCode == Keys.PageDown)
                {
                    if (!picMan.toNext())
                    {
                        MessageBox.Show("图片已经处理完了。");
                    }
                        
                }
                else if (e.KeyCode == Keys.PageUp)
                {
                    if (!picMan.toPrev())
                    {
                        MessageBox.Show("已经到最前面了。");
                    }                    
                }
                showRect = false;
                rectList.Clear();
                pointList.Clear();
                showPic();
            }
            else
            {
                //视频处理
                if (e.KeyCode == Keys.PageDown)
                {
                    if (!vidMan.toNext())
                    {
                        MessageBox.Show("视频已经结束。");
                    }
                }
                else if (e.KeyCode == Keys.PageUp)
                {
                    if (!vidMan.toPrev()){
                        MessageBox.Show("视频已经到达开始。");
                    }                                         
                }
                showRect = false;
                rectList.Clear();
                pointList.Clear();
                showVideo();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_OpenVideo_Click(object sender, EventArgs e)
        {
            //初始化工作           

            OpenFileDialog openVideo = new OpenFileDialog();
            openVideo.Filter = "*|*.*";
            

            if (openVideo.ShowDialog() == DialogResult.OK)
            {
                
                if (openVideo.FileName != "")
                {
                    isVideo = true;
                    vidMan = new VideoManager(openVideo.FileName);
                    btnSetPos.Visible = true;
                    txtSetPos.Visible = true;
                }
                else
                {
                    MessageBox.Show("文件不存在!");
                }

                frameList.Clear();

                showVideo();
            }
        }


        /**
         * 显示当前filePoint指向的文件
         * */
        private void showVideo()
        {
            if (vidMan.isFinished())
            {
                MessageBox.Show("视频已经处理完了");
                return;
            }

            pbxOriImage.Image =  vidMan.getFrame().ToBitmap();
            lblPos.Text = "第 "+ (vidMan.FramePoint+1) + "/" + vidMan.TotalFrames+ " 帧";
            this.SetImageSizeMode(pbxOriImage);
            
            pbxOriImage.Invalidate();
        }

        private void btnSetPos_Click(object sender, EventArgs e)
        {
            int count = int.Parse(txtSetPos.Text);
            if (count < 1 || count > vidMan.TotalFrames)
            {
                MessageBox.Show("你设置的帧数不在有效范围内！！");
                txtSetPos.Text = "1";
                return;
            }
            vidMan.FramePoint = count - 1;
            frameList.Clear();
            rectList.Clear();
            showVideo();
        }

    }
}
